package com.openatc.comm.ocp;
/**
 * @ClassName: DataSchedulePackUpPack
 * @Description:
 * @author liangting
 * @date 2019年10月30日 上午10:16:06
 */


import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.ToNumberPolicy;
import com.openatc.comm.data.MessageData;
import com.openatc.comm.data.MessageDataMD5;
import com.openatc.core.model.InnerError;
import com.openatc.core.util.Gzip;
import com.openatc.core.util.RESTRetUtils;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.Arrays;

import static com.openatc.comm.common.CosntDataDefine.*;
import static com.openatc.core.common.IErrorEnumImplInner.E_207;
import static com.openatc.core.common.IErrorEnumImplInner.E_209;

public class OcpDataPackUpPack { //数据表内容宏定义


    private static final int MAX_DATA_SCHEDULE = 14;         //数据表大小
    private static final int ReadDataScheduleSuccess = 1;
    private static final int ReadDataScheduleFault = 3;
    private static final int SCHEDULEBUFFER = 10 * 1024;

    // 修改gson 将 int long 等数字转化为小数
    private static Gson  gson = new GsonBuilder()
            .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
            .create();

    public void DataSchedulePackUpPack() {

    }

    //操作类型字符串
    public static String StringOperatorType(byte operatorNumber) {
        switch (operatorNumber) {
            case -128:
                return getrequest;
            case -125:
                return getresponse;
            case -127:
                return setrequest;
            case -124:
                return setresponse;
            case -126:
                return report;
            case -123:
                return erroranswer;
        }
        return erroranswer;
    }

    public static String StringOperatorObj(byte operatorObj) {
        switch (operatorObj) {
            case 0x01:
            case 0x51:
                return login;
            case 0x02:
                return volume;
            case 0x03:
                return workstatus;
            case 0x04:
                return lampcolor;
            case 0x05:
                return time;
            case 0x06:
                return signalgroup;
            case 0x07:
                return phase;
            case 0x08:
                return timepattern;
            case 0x09:
                return scheduleplan;
            case 0x0A:
                return ControlPattern;
            case 0x0B:
                return falut;
            case 0x0C:
                return atcversion;
            case 0x0D:
                return paramversion;
            case 0x0E:
                return code;
            case INFO_TYPE_REMOTE_CONTROL:
                return remote;
            case 0x10:
                return detector;
            case 0x11:
                return overlap;
            case 0x12:
                return scheduledate;
            case 0x24:
            case 0x23:
            case 0x22:
            case 0x18:
                return allfeature;
            case -94:
                return paramip;
            case -93:
                return serialport;
            case -92:
                return manualpanel;
            case -91:
                return updatedisk;
            case -90:
                return systemremote;
            case -89:
                return systemlog;
            case -88:
                return channelcheck;
            case -87:
                return volumelog;
            case -86:
                return interrupt;
            case -85:
                return channelstatus;
            case -84:
                return channellampstatus;
            case -83:
                return systemcustom;
            case INFO_TYPE_SYSTEM_UPDATE:
                return systemupdate;
            case INFO_TYPE_DETECTOR_STATUS:
                return detectorstatus;
            case INFO_TYPE_SECRETKEY:
                return secretkeyupdate;
        }

        return null;
    }


    /**
     * @return
     * @throws
     * @author laoxia
     * description 设置第八个字节位数据
     * create time 2021/8/23 13:51
     */
    private byte dataScheduleEight(String operationType, String infoType) {
        byte eight = (byte) 0x00;
        switch (operationType) {
            case getresponse://设置应答
                eight = INFO_TYPE_ONLINE;
                break;
            case setresponse://查询应答
                eight = INFO_TYPE_ONLINE;
                break;
            default:
        }

        // 如果是应答操作，返回对象标识；否则则是查询操作，继续执行
        if(eight == INFO_TYPE_ONLINE )
            return eight;

        switch (infoType) {
            case allfeature:
                // 全部参数上下载需要特殊处理
                if (operationType.equals(getrequest))
                    eight = CFG_ASK_ASKREAD;
                if (operationType.equals(setrequest))
                    eight = CFG_ASK_SENDDATA;
                break;
            case login: //上位机发login请求
                eight = INFO_TYPE_LOGIN;
                //数据内容:上位机联机请求
                break;
            case volume: //上位机查询交通流请求
                eight = INFO_TYPE_TRAFFIC_FLOW;
                //数据内容:上位机查询交通流
                break;
            case workstatus://工作状态查询
                eight = INFO_TYPE_WORK_STATE;
                break;
            case lampcolor://灯色状态查询
                eight = INFO_TYPE_LAMP_COLOR;
                break;
            case time://信号机时间
                eight = INFO_TYPE_CURRENT_TIME;
                break;
            case signalgroup://信号灯组信息
                eight = INFO_TYPE_SIGNAL_GROUP;
                break;
            case phase://相位信息
                eight = INFO_TYPE_PHASE;
                break;
            case overlap://跟随相位信息
                eight = INFO_TYPE_OVERLAP;
                break;
            case timepattern://信号配时方案
                eight = INFO_TYPE_SCHEME_PARTTERN;
                break;
            case scheduleplan://方案调度计划
                eight = INFO_TYPE_SCHEDUL_PLAN;
                break;
            case scheduledate://方案调度计划
                eight = INFO_TYPE_SCHEDUL_DATE;
                break;
            case ControlPattern://工作方式
                eight = INFO_TYPE_WORK_MODE;
                break;
            case falut://信号机故障
                eight = INFO_TYPE_SIGNAL_ERROR;
                break;
            case atcversion://信号机版本
                eight = INFO_TYPE_ATC_VERSION;
                break;
            case paramversion://特征参数版本
                eight = INFO_TYPE_PARAM_VERSION;
                break;
            case code://信号机识别码
                eight = INFO_TYPE_IDENTIFY_CODE;
                break;
            case detector://检测器
                eight = INFO_TYPE_DETECTOR;
                break;
            case manualpanel:// 手动面板
                eight = INFO_TYPE_MANUALPANEL;
                break;
            case systemremote:// 远程调试
                eight = INFO_TYPE_SYSTEM_REMOTE;
                break;
            case systemlog:// 操作日志
                eight = INFO_TYPE_SYSTEM_LOG;
                break;
            case channelstatus:// 通道状态（电压电流）
                eight = INFO_TYPE_CHANNEL_STATUS;
                break;
            case channellampstatus:// 通道灯色状态
                eight = INFO_TYPE_CHANNEL_LAMP_STATUS;
                break;
            case systemcustom:// 设备参数
                eight = INFO_TYPE_SYSTEM_CUSTOM;
                break;
            case channelcheck:// 通道检测
                eight = INFO_TYPE_SYSTEM_CHANNEL_CHECK;
                break;
            case interrupt:// 方案干预
                eight = INFO_TYPE_PATTERN_INTERRUPT;
                break;
            case remote:// 远程控制
                eight = INFO_TYPE_REMOTE_CONTROL;
                break;
            case systemupdate:// 远程控制
                eight = INFO_TYPE_SYSTEM_UPDATE;
                break;
            case secretkeyupdate:// 更新密钥
                eight = INFO_TYPE_SECRETKEY;
                break;
            default:
        }
        return eight;
    }

    /**
     * @return
     * @throws
     * @author laoxia
     * description 设置第七个字节位数据
     * create time 2021/8/23 13:51
     */
    private byte dataScheduleSeven(String operationType, String infoType) {
        byte seven = (byte) 0x00;
        switch (operationType) {
            case getrequest:
                switch (infoType) {
                    case login:
                        seven = OPERATE_TYPE_SET_REQUEST;
                        break;
                    default:
                        seven = OPERATE_TYPE_QUERY_REQUEST;
                }
                break;
            case setrequest:
                seven = OPERATE_TYPE_SET_REQUEST;
                break;
            case getresponse:
                seven = OPERATE_TYPE_SET_ANSWER;
                break;
            case setresponse:
                seven = OPERATE_TYPE_SET_REQUEST;
                break;
            default:
        }
        return seven;
    }

    /**
     * @return
     * @throws
     * @author laoxia
     * description 设置第三个字节位数据
     * create time 2021/8/23 13:51
     */
    private byte dataScheduleThree(String operationType, String infoType) {
        byte three = (byte) 0x00;
        switch (operationType) {
            case getresponse://设置应答
                three = DATA_LINK_COMMU_SPE;
                break;
            case setresponse://查询应答
                three = DATA_LINK_COMMU_SPE;
                break;
            default:
        }

        // 如果是应答操作，返回链路码；否则则是查询操作，继续执行
        if(three == DATA_LINK_COMMU_SPE )
            return three;

        switch (infoType) {
            case login: //上位机发login请求
                three = DATA_LINK_CONTROL;
                //数据内容:上位机联机请求
                break;
            case volume: //上位机查询交通流请求
                three = DATA_LINK_CONTROL;//链路5
                //数据内容:上位机查询交通流
                break;
            case workstatus://工作状态查询
                three = DATA_LINK_BASIC_INFO;
                break;
            case lampcolor://灯色状态查询
                three = DATA_LINK_BASIC_INFO;
                break;
            case time://查询信号机时间
                three = DATA_LINK_BASIC_INFO;
                break;
            case signalgroup://查询信号灯组信息
                three = DATA_LINK_PARAM_TRAMFER;
                break;
            case phase://查询相位信息
                three = DATA_LINK_PARAM_TRAMFER;
                break;
            case overlap://查询跟随相位信息
                three = DATA_LINK_PARAM_TRAMFER;
                break;
            case timepattern://查询信号配时方案
                three = DATA_LINK_PARAM_TRAMFER;
                break;
            case scheduleplan://查询方案调度计划
                three = DATA_LINK_PARAM_TRAMFER;
                break;
            case scheduledate://查询日期
                three = DATA_LINK_PARAM_TRAMFER;
                break;
            case ControlPattern://查询工作方式
                three = DATA_LINK_INTER_ORDER;
                break;
            case falut://查询信号机故障
                three = DATA_LINK_BASIC_INFO;
                break;
            case atcversion://查询信号机版本
                three = DATA_LINK_BASIC_INFO;
                break;
            case paramversion://查询特征参数版本
                three = DATA_LINK_INTER_ORDER;
                break;
            case code://信号机识别码
                three = DATA_LINK_INTER_ORDER;
                break;
            case detector://检测器
                three = DATA_LINK_INTER_ORDER;
                break;
            case allfeature://查询整体参数feature/all
                three = DATA_LINK_CONFIG;//配置软件和信号机的参数命令
                break;
            case manualpanel:// 查询手动面板
                three = DATA_LINK_CONTROL;
                break;
            case systemremote:// 查询远程调试
                three = DATA_LINK_CONTROL;
                break;
            case systemlog:// 查询操作日志
                three = DATA_LINK_CONTROL;
                break;
            case channelstatus:// 查询通道状态（电压电流）
                three = DATA_LINK_CONTROL;
                break;
            case channellampstatus:// 查询通道灯色状态
                three = DATA_LINK_CONTROL;
                break;
            case systemcustom:// 查询设备参数
                three = DATA_LINK_PARAM_TRAMFER;
                break;
            case interrupt:// 查询设备参数
                three = DATA_LINK_CONTROL;
                break;
            case remote:// 远程控制
                three = DATA_LINK_INTER_ORDER;
                break;
            case systemupdate:// 远程控制
                three = DATA_LINK_PARAM_TRAMFER;
                break;
            case channelcheck: // 通道检测
                three = DATA_LINK_CONTROL;
                break;
            case secretkeyupdate: // 更新密钥
                three = DATA_LINK_CONTROL;
                break;
            default:
        }
        return three;
    }


    private byte[] PackDataContent(MessageData sendData, byte[] dataSchdule ) throws UnsupportedEncodingException {
        String infoType = sendData.getInfotype();
        switch (infoType) {
            case allfeature://下载整体参数
                //数据内容
                int dataSendCount = 0;
                JsonElement data = sendData.getData();
                String datamd5value = null;
                if (data != null) {
                    DataParamMD5 dataMD5 = new DataParamMD5();
                    datamd5value = dataMD5.getMD5(data.toString());
                }
                MessageDataMD5 md5data = new MessageDataMD5(sendData);
                md5data.setMd5(datamd5value);
                String dataSetAllParam = gson.toJson(md5data);
                if (dataSetAllParam != null) {
                    byte[] dataSend = dataSetAllParam.getBytes("UTF-8");
                    dataSendCount = dataSend.length;
                    dataSchdule = Arrays.copyOf(dataSchdule, dataSendCount + 14);
                    System.arraycopy(dataSend, 0, dataSchdule, 14, dataSendCount);
                }
                for (int i = 0; i < 4; i++) {
                    dataSchdule[i + 10] = (byte) ((dataSendCount >> (i * 8)) & 0xFF);
                }
                break;
            default:
                dataSchdule = AddMsgToDatafillwithLength(sendData,dataSchdule);
        }
        return dataSchdule;
    }

    /**
     *
     * 将数据填充到dataSchdule
     *
     * @param sendData
     */
//    public void AddMsgToData(MessageData sendData) throws UnsupportedEncodingException {
//        String data;
//        JsonElement dataLoginObject = sendData.getData();
//        if (dataLoginObject != null) {
//            data = dataLoginObject.toString();
//            byte[] dataSend = data.getBytes("UTF-8");
//            int dataSendCount = dataSend.length;
//            dataSchdule = Arrays.copyOf(dataSchdule, dataSendCount + 14);
//            System.arraycopy(dataSend, 0, dataSchdule, 14, dataSendCount);
//        }
//    }

    /**
     *
     * 将数据填充到dataSchdule
     * 用于参数下载消息
     * @param sendData
     */
    public byte[] AddMsgToDatafillwithLength(MessageData sendData, byte[] dataSchdule) throws UnsupportedEncodingException {
        int dataSendCount = 0;
        JsonElement messageDataContent = sendData.getData();
        if (messageDataContent != null) {
            String strSend = messageDataContent.toString();
            if (strSend != null) {
                byte[] dataSend = strSend.getBytes("UTF-8");
                dataSendCount = dataSend.length;
                dataSchdule = Arrays.copyOf(dataSchdule, dataSendCount + 14);
                System.arraycopy(dataSend, 0, dataSchdule, 14, dataSendCount);
            }
            for (int i = 0; i < 4; i++) {
                dataSchdule[i + 10] = (byte) ((dataSendCount >> (i * 8)) & 0xFF);
            }
            return dataSchdule;
        }
        return dataSchdule;
    }

    /**
     * @param sendData 发给信号机的数据
     * @return byte[] 转换为数据表的结果
     * @Title: PackDataSchedule
     * @Description: 根据国标对发送数据进行协议转化（转换为数据表的结构）
     */
    public byte[] PackDataSchedule(MessageData sendData) throws UnsupportedEncodingException {
        byte[] dataSchdule = new byte[MAX_DATA_SCHEDULE];

        String operationType = sendData.getOperation();
        String infoType = sendData.getInfotype();
        // 将SiteID转换为2个字节
        int siteid = Integer.parseInt(sendData.getThirdpartyid());
        ByteBuffer bb= ByteBuffer.allocate(4);
        bb.asIntBuffer().put(siteid);

        Arrays.fill(dataSchdule, (byte) 0);
        dataSchdule[0] = CB_VERSION_FLAG;
        dataSchdule[1] = CB_RECEIVE_FLAG;
        dataSchdule[2] = CB_SEND_FLAG;
        dataSchdule[3] = dataScheduleThree(operationType, infoType);// 数据链路码
        dataSchdule[4] = 0x00;// 区域号
        dataSchdule[5] = bb.get(2);// 路口号
        dataSchdule[6] = bb.get(3);// 路口号
        dataSchdule[7] = dataScheduleSeven(operationType, infoType);// 操作类型
        dataSchdule[8] = dataScheduleEight(operationType, infoType);// 对象标识
        dataSchdule[9] = (byte)sendData.getAuth().intValue();
        dataSchdule[10] = 0x00;
        dataSchdule[11] = 0x00;
        dataSchdule[12] = 0x00;
        dataSchdule[13] = 0x00;
        if(operationType.equals(getrequest)) // 查询消息没有消息体，直接返回消息头
            return dataSchdule;
        else
            return PackDataContent(sendData, dataSchdule); // 设置消息需要拼上消息体
    }

    //应答信号的数据表
    public static byte[] AskPackDataSchedule(String  operationtype) {
        byte[] dataSchdule = new byte[MAX_DATA_SCHEDULE];

//        String operationtype = sendData.getOperation();
//        String infotype = sendData.getInfotype();
        dataSchdule[0] = CB_VERSION_FLAG;
        dataSchdule[1] = CB_RECEIVE_FLAG;
        dataSchdule[2] = CB_SEND_FLAG;
        dataSchdule[3] = DATA_LINK_COMMU_SPE;
        dataSchdule[7] = OPERATE_TYPE_SET_ANSWER;
        dataSchdule[8] = INFO_TYPE_ONLINE;
        dataSchdule[9] = RESERVE_FLAG;
        dataSchdule[10] = 0x00;
        dataSchdule[11] = 0x00;
        dataSchdule[12] = 0x00;
        dataSchdule[13] = 0x00;
        return dataSchdule;
    }


    /**
     * @return 1->成功  3->失败
     * @Description 数据检查
     * @Date 2021/8/23 15:27
     */
    private static int checkDataLink(byte chDataLink, byte chOperateType, byte chInfoType, String tempData, MessageData recvData) {
        //信号机应答整体参数设置
        if ((chOperateType == OPERATE_TYPE_SET_ANSWER) && (chInfoType == CFG_ACK_SENDDATA_FAILED)) {
            recvData.setOperation(StringOperatorType(OPERATE_TYPE_ERROR_ANSWER));
        }
        else if ((chOperateType == OPERATE_TYPE_SET_REQUEST) && (chInfoType == INFO_TYPE_ONLINE)) {
            recvData.setOperation(StringOperatorType(OPERATE_TYPE_REPORT));
        }
        return ReadDataScheduleSuccess;
    }

    /**
     * @param recvData 返还上位机参数,pSource 待解析数据表数据, SourceLength 数据表有效长度
     * @return int  数据表解析状态
     * @Title: ReadDataSchedule
     * @Description: 对数据表进行解析，并将解析内容转发配置软件
     */
    public static int ReadDataSchedule(MessageData recvData, byte[] dataSchedule, int dataScheduleSize) throws UnsupportedEncodingException {
        String tempData = "";
        if (dataScheduleSize >= 14) {
            // 处理gzip压缩的数据内容
            byte gzip = dataSchedule[10];
            if (gzip == 1) {
                byte[] subArray = Arrays.copyOfRange(dataSchedule, 14, dataScheduleSize);
                tempData = Gzip.uncompressToString(subArray, Gzip.GZIP_ENCODE_UTF_8);
            } else {
                tempData = new String(dataSchedule ,14, dataScheduleSize-14, "UTF-8");
            }
        }else
            return 0;

        // 解析设备ID
        String roadID;
        int int1 = dataSchedule[5] & 0xff;
        int int2 = (dataSchedule[6] & 0xff) << 8;
        int roadValue = int1 | int2;
        roadID = String.valueOf(roadValue);
        byte chDataLink = dataSchedule[3];
        byte chOperateType = dataSchedule[7];
        byte chInfoType = dataSchedule[8];

        recvData.setInfotype(StringOperatorObj(chInfoType));
        recvData.setOperation(StringOperatorType(chOperateType));
        recvData.setThirdpartyid(roadID);

        // 组装消息体
        if(chInfoType == CFG_ACK_SENDDATA_FAILED){ // 下载全部参数错误
            InnerError innerError = RESTRetUtils.innerErrorObj(null,E_207,gson.fromJson(tempData, JsonElement.class));
            recvData.setData(gson.toJsonTree(innerError));
        }else if(chOperateType == OPERATE_TYPE_ERROR_ANSWER){ // 错误应答
            InnerError innerError;
            if( allfeature.equals(recvData.getInfotype()) )
                innerError = RESTRetUtils.innerErrorObj(null,E_207,gson.fromJson(tempData, JsonElement.class));
            else
                innerError = RESTRetUtils.innerErrorObj(null,E_209,gson.fromJson(tempData, JsonElement.class));

            recvData.setData(gson.toJsonTree(innerError));
        }
        else if (tempData != null) {
            JsonElement obj = gson.fromJson(tempData, JsonElement.class);
            recvData.setData(obj);
        } else {
            tempData = "{\n" +
                    "\t\"success\": -1\n" +
                    "}";
            JsonElement obj = gson.fromJson(tempData, JsonElement.class);
            recvData.setData(obj);
        }
        return checkDataLink(chDataLink, chOperateType, chInfoType, tempData, recvData);
    }

    public boolean isZero(byte[] dataSchedule) {
        for (int i = 0; i < dataSchedule.length; i++) {
            if (dataSchedule[i] != 0) {
                return false;
            }
        }
        return true;
    }
}

